/*************************************************
func: 封装socket服务端接口，实现tcp或udp通信
author: zyh
date: 2024.4
**************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include "socket_api.h"
#include "socket_server_api.h"

//打印的函数，可重定义：比如换成记录日志等
#define PRINTF_MSG(msg, ...) printf("[INFO]<%s:%d>: " msg "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#define PRINTF_ERR_MSG(msg, ...) fprintf(stderr, "[ERROR]<%s:%d>: " msg "\n", __FILE__, __LINE__, ##__VA_ARGS__)

#if 1
/*链表存储客户端信息接口---------------------------------------*/
// 初始化链表
static void init_list(client_link_list_t* list) {
    list->head = NULL;
    list->tail = NULL;
    list->count = 0;
    pthread_mutex_init(&list->lock, NULL);
}

// 销毁链表
static void destroy_list(client_link_list_t* list) {
    pthread_mutex_lock(&list->lock);
    client_node_t* current = list->head;
    while(current != NULL) {
        client_node_t* next = current->next;
        free(current);
        current = next;
    }
    list->head = NULL;
    list->tail = NULL;
    list->count = 0;
    pthread_mutex_unlock(&list->lock);
    pthread_mutex_destroy(&list->lock);
}

// 尾插法
static void push_back(client_link_list_t* list, client_data_t data) {
    pthread_mutex_lock(&list->lock);
    client_node_t* newNode = (client_node_t*)malloc(sizeof(client_node_t));
    newNode->data = data;
    newNode->next = NULL;
    if (list->tail == NULL) {
        list->head = newNode;
        list->tail = newNode;
    } else {
        list->tail->next = newNode;
        list->tail = newNode;
    }
    list->count++;
    pthread_mutex_unlock(&list->lock);
}

#if 0
// 头插法
static void push_front(client_link_list_t* list, client_data_t data) {
    pthread_mutex_lock(&list->lock);
    client_node_t* newNode = (client_node_t*)malloc(sizeof(client_node_t));
    newNode->data = data;
    newNode->next = list->head;
    list->head = newNode;
    if (list->tail == NULL) {
        list->tail = newNode;
    }
    list->count++; // 增加节点计数
    pthread_mutex_unlock(&list->lock);
}

// 获取头节点
static client_node_t* get_head(client_link_list_t* list) {
    return list->head;
}
#endif

// 获取尾节点
static client_node_t* get_tail(client_link_list_t* list) {
    return list->tail;
}

// 删除节点
static void delete_socket_client(client_link_list_t* list, int socket_fd) {
    pthread_mutex_lock(&list->lock); // 锁定互斥锁
    client_node_t* current = list->head;
    client_node_t* prev = NULL;

    while (current != NULL && current->data.socket_fd != socket_fd) {
        prev = current;
        current = current->next;
    }

    if (current == NULL) { // 没找到数据
        pthread_mutex_unlock(&list->lock); // 解锁互斥锁
        return;
    }

    if (current == list->head) {// 删除的是头节点
        list->head = current->next;
        if (current == list->tail) {
            list->tail = NULL;
        }
    } else if (current == list->tail) {// 如果删除的是尾节点，则需要更新尾节点指针
        list->tail = prev;
        list->tail->next = NULL;
    } else {
        prev->next = current->next;
    }
    
    free(current);
    list->count--;
    pthread_mutex_unlock(&list->lock);
}

//获取某个客户端信息，失败返回NULL
client_data_t* get_socket_client(client_link_list_t* list, int socket_fd) {
    pthread_mutex_lock(&list->lock);
    client_node_t* current = list->head;
    while (current != NULL) {
        if (current->data.socket_fd == socket_fd) {
            pthread_mutex_unlock(&list->lock);
            return &current->data;
        }
        current = current->next;
    }
    pthread_mutex_unlock(&list->lock);
    return NULL;
}


// 打印链表中客户端信息
void print_socket_client_list(client_link_list_t* list) {
    pthread_mutex_lock(&list->lock);
    client_node_t* current = list->head;
    int i = 0;
    while (current != NULL) {
		i++;
        client_data_t client = current->data;
        printf("client %d, socket_fd: %d, ip: %s, port: %d\n", i, client.socket_fd, client.ip, client.port);
        current = current->next;
    }
    pthread_mutex_unlock(&list->lock);
}


#endif


static void *process_client(void *ptr)
{
	socket_server_t *server = (socket_server_t *)ptr;
    
    client_node_t* tail = get_tail(&server->client_list);
    if (NULL == tail) {
		PRINTF_ERR_MSG("socket get_tail failed");
        return NULL;
    }
        
    client_data_t client = tail->data;
    int client_fd = client.socket_fd;
	//PRINTF_MSG("(socket client_fd=%d)\n", client_fd);
    
    int rcv_buf_size = (0 < server->rcv_buf_size) ? server->rcv_buf_size : 1024;
    //int rcv_flag = 0;
    int rcv_len = 0;
    unsigned char *rcv_buf = (unsigned char *)calloc(1, rcv_buf_size);
	if (!rcv_buf) {
		PRINTF_ERR_MSG("calloc rcv_buf failed");
		goto done;
	}
    
    set_fd_nonblock(client_fd);//设置客户端fd为非堵塞
	while (server->run_flag) {
		memset(rcv_buf, 0, rcv_buf_size);
		rcv_len = socket_rcv_nonblock(client_fd, rcv_buf, rcv_buf_size - 1, 100);
		if(-1 == rcv_len) {
			PRINTF_ERR_MSG("socket_rcv_nonblock failed (client_fd=%d)", client_fd);
		} else if(0 == rcv_len) {
			PRINTF_MSG("socket client exit (client_fd=%d)", client_fd);
            delete_socket_client(&server->client_list, client_fd);//从链表中删除
            if (0 < client_fd) {//关闭客户端socket
                close(client_fd);
                //client_fd = -1;
            }
            
            if (server->client_onExit) {//客户端退出时的外部回调函数
                server->client_onExit(server, client);
            }
            
			break;//退出当前子线程
		} else if ( 0 < rcv_len) {
            //PRINTF_MSG("%s\n", rcv_buf);
            if (server->client_onMessage) {//接收客户端消息时的外部回调函数
                server->client_onMessage(server, client, (void *)rcv_buf, rcv_len);
            }
        }
	}
	    
done:
	if (rcv_buf) {
		free(rcv_buf);
		rcv_buf = NULL;
	}
	PRINTF_MSG("exit server_process_client (client_fd=%d)", client_fd);
	return NULL;
}


//普通socket服务端服务（为每个客户端创建独立子线程）
static void *socket_server_loop(void *arg)
{
	socket_server_t *server = (socket_server_t *)arg;
    
    server->max_listen_backlog = (0 < server->max_listen_backlog) ? server->max_listen_backlog : 128;
    server->max_client_online_num = (0 < server->max_client_online_num) ? server->max_client_online_num : 128;
    init_list(&server->client_list);
	client_data_t client = {0};
    
	int listen_fd = -1;
	int client_fd = -1;
	struct sockaddr_in client_addr = {0};
	socklen_t addrlen = sizeof(client_addr);
    int flag = 0;
        
	PRINTF_MSG("start socket_server, ip=%s, port=%d, max_client_online_num=%d", server->ip, server->bind_port, server->max_client_online_num);
	while (server->run_flag) {
        if (0 == flag) {
            if (0 < listen_fd) {
                close(listen_fd);
                listen_fd = -1;
            }
            //服务端socket初始化：创建、绑定、监听
            listen_fd = socket_server_init(server->socket_type, server->socket_protocol, server->broadcast_flag, server->ip, server->bind_port, server->max_listen_backlog);
            if (0 > listen_fd) {
                PRINTF_ERR_MSG("socket_server_init failed, %d端口可能被占用", server->bind_port);
                sleep(1);
                continue;
            }
            //设置socket为非堵塞
            if (0 > set_fd_nonblock(listen_fd)) {
                PRINTF_MSG("set_fd_nonblock failed");
                continue;
            }
            
            flag = 1;
        } else {
            if (server->client_list.count >= server->max_client_online_num) {//客户端在线数量过多，不再接收了
                PRINTF_MSG("clients are too many, refuse connect (max_client_online_num=%d, client_online_count=%d)", server->max_client_online_num, server->client_list.count);
                usleep(100*1000);
                continue;
            }
            
            if (0 > select_fd(listen_fd, 100)) {//100ms监听
                continue;
            }
            client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &addrlen);
            //已经处理完所有连接，accept返回-1，errno为EAGAIN，accept出错返回-1，errno另有其值
            if (-1 == client_fd) {
                if (errno != EAGAIN && errno != ECONNABORTED 
                        && errno != EPROTO && errno != EINTR) {
                    perror("accept");
                }
            } else if (0 < client_fd) {
                client.socket_fd = client_fd;
                snprintf(client.ip, sizeof(client.ip), "%s", inet_ntoa(client_addr.sin_addr));
                client.port = ntohs(client_addr.sin_port);
                push_back(&server->client_list, client);//客户端信息加入链表
                //PRINTF_MSG("New Client Login, IP=%s Port=%d fd=%d client_online_count=%d", client.ip, client.port, client.socket_fd, server->client_list.count);
                if (server->client_onLogin) {//客户端登录时的回调函数
                    server->client_onLogin(server, client);
                }
                
                pthread_t thread_id;
                pthread_create(&thread_id, NULL, process_client, (void *)server);
                pthread_detach(thread_id);//将线程分离, 线程结束后自动释放线程资源，后续不需要使用pthread_join()进行回收
                usleep(10*1000);
            }
        }
	}

	if (listen_fd > 0) {
		close(listen_fd);
	}
    
    client_node_t* current = server->client_list.head;//关闭所有客户端连接
    while (current != NULL) {
        if (0 < current->data.socket_fd) {
            close(current->data.socket_fd);
        }
        current = current->next;
    }
    destroy_list(&server->client_list);
    
	PRINTF_MSG("exit epoll_socket_server");
	return NULL;
}




//基于epoll方式实现的socket服务端（IO多路复用，暂时是单线程处理多个客户端，后续再优化成多个线程）
static void *epoll_socket_server_loop(void *arg)
{
	socket_server_t *server = (socket_server_t *)arg;
    server->max_listen_backlog = (0 < server->max_listen_backlog) ? server->max_listen_backlog : 128;
    server->max_client_online_num = (0 < server->max_client_online_num) ? server->max_client_online_num : 128;
    init_list(&server->client_list);
    
	client_data_t client = {0};
    
	int ep_fd = 0;
	int max_epoll_events_num = server->max_client_online_num;//epoll同时监听的最大事件数量

	struct epoll_event ev = {0};
	int i = 0, num_events = 0;
    struct epoll_event *events = NULL;//events[max_epoll_events_num]

	int listen_fd = 0;
	int client_fd = 0;
	struct sockaddr_in client_addr = {0};
	socklen_t addrlen = sizeof(client_addr);
	    
    int rcv_buf_size = (0 < server->rcv_buf_size) ? server->rcv_buf_size : 1024;
    //int rcv_flag = 0;//1接收到数据，0未接收到
    int rcv_len = 0;
    unsigned char *rcv_buf = (unsigned char *)calloc(1, rcv_buf_size);
	if (!rcv_buf) {
		PRINTF_ERR_MSG("calloc rcv_buf failed");
		goto done;
	}


	//服务端socket初始化：创建、绑定、监听
	listen_fd = socket_server_init(server->socket_type, server->socket_protocol, server->broadcast_flag, server->ip, server->bind_port, server->max_listen_backlog);
	if (0 > listen_fd) {
		PRINTF_ERR_MSG("socket_server_init failed, %d端口可能被占用", server->bind_port);
		goto done;
	}
	set_fd_nonblock(listen_fd);//服务器应该使用非阻塞的accept，所以listen_fd设置为非堵塞

    //ep_fd = epoll_create(max_epoll_events_num);//创建一个 epoll 实例（epoll 文件描述符），以用于 I/O 多路复用
    ep_fd = epoll_create1(0);//创建了一个 epoll 实例
	if (0 > ep_fd) {
        PRINTF_ERR_MSG("epoll_create1");
		goto done;
    } 
 
/*events : 事件类型
EPOLLIN	文件描述符是否可读
EPOLLOUT	文件描述符是否可写
EPOLLRDHUP	对端关闭连接（被动），或者套接字处于半关闭状态（主动），这个事件会被触发。当使用边缘触发模式时，很方便写代码测试连接的对端是否关闭了连接
EPOLLPRI	文件描述符是否异常
EPOLLERR	文件描述符是否错误。如果文件描述符已经关闭，继续写入也会收到这个事件。这个事件用户不设置也会被上报
EPOLLHUP	套接字被挂起，这个事件用户不设置也会被上报
EPOLLET	设置epoll的触发模式为边缘触发模式。如果没有设置这个参数，epoll默认情况下是水平触发模式
水平触发：这是epoll默认的触发方式，既支持阻塞模式，也支持非阻塞模式，当被监控的文件描述符上有可读写事件发生时，epoll_wait()会通知处理程序去读写。
			如果这次没有把数据一次性全部读写完(如读写缓冲区太小)，那么下次调用 epoll_wait()时，它还会通知你在上次没读写完的文件描述符上继续读写。
			即可读一直通知读，可写一直通知写。
边缘触发： 当被监控的文件描述符上有可读写事件发生时，epoll_wait()会通知处理程序去读写。
			如果这次没有把数据全部读写完(如读写缓冲区太小)，那么下次调用epoll_wait()时，
			它不会通知你，也就是它只会通知你一次，直到该文件描述符上出现第二次可读写事件才会通知你。
			即可读可写时，只会通知一次。
			
	总体来说，边沿触发模式相比于水平触发模式更为高效，并且可以避免由于重复监听导致 CPU 占用率过高的问题，
	一般用于数据量很大，需要分批次接收的时候。但是，在使用边沿触发模式时需要注意及时读取所有数据，并确保每个事件都得到了正确处理。
*/
/*op：需要执行的操作，添加，修改，删除
EPOLL_CTL_ADD：向 epoll 实例添加一个文件描述符，并监视指定的事件。
EPOLL_CTL_MOD：修改 epoll 实例中已注册的文件描述符的事件。
EPOLL_CTL_DEL：从 epoll 实例中删除一个文件描述符，不再监视其事件。
*/
    ev.events = EPOLLIN;//可读事件。如果多个连接同时到达，出于对丢失连接的考虑，listen_fd的事件监听默认是水平触发模式
    ev.data.fd = listen_fd;
    if (0 > epoll_ctl(ep_fd, EPOLL_CTL_ADD, listen_fd, &ev)) {//添加事件
        PRINTF_ERR_MSG("epoll_ctl: EPOLL_CTL_ADD");
		goto done;
    }
	
    events = (struct epoll_event *)calloc(max_epoll_events_num, sizeof(struct epoll_event));
	if (!events) {
		PRINTF_ERR_MSG("calloc epoll_event failed, epoll_events_num=%d", max_epoll_events_num);
		goto done;
	}

	PRINTF_MSG("start epoll_socket_server, ip=%s, port=%d, max_client_online_num=%d", server->ip, server->bind_port, server->max_client_online_num);
	while (server->run_flag) {
		num_events = epoll_wait(ep_fd, events, max_epoll_events_num, 100);//等待事件发生（超时时间ms, -1表示阻塞等待, 0表示立即返回, 大于0表示在指定时间内有事件发生则返回）
        if (0 > num_events) {
            perror("epoll_wait");
            continue;
        }
 
        for (i = 0; i < num_events; i++) {
            if (events[i].data.fd == listen_fd) {//如果是监听的listen_fd，那就是客户端的连接
            	if (server->client_list.count >= server->max_client_online_num) {//客户端在线数量过多了，不再接收了
                    PRINTF_MSG("clients are too many, refuse connect (max_client_online_num=%d, client_online_count=%d)", server->max_client_online_num, server->client_list.count);
					continue;
				}
				//为提高效率，每次处理一个连接，while循环直到处理完所有的连接
				while (0 < (client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &addrlen))) {
					client.socket_fd = client_fd;
					snprintf(client.ip, sizeof(client.ip), "%s", inet_ntoa(client_addr.sin_addr));
					client.port = ntohs(client_addr.sin_port);
					push_back(&server->client_list, client);//客户端信息加入链表
					//PRINTF_MSG("New Client Login, IP=%s Port=%d fd=%d client_online_count=%d", client.ip, client.port, client.socket_fd, server->client_list.count);

					if (server->client_onLogin) {//客户端登录时的回调函数
						server->client_onLogin(server, client);
					}

					/*将客户端fd加入epoll中进行检测:
					水平触发模式下：当socket可写时，会不停的触发socket可写事件，文件描述符仍会不停地返回就绪，效率不高。
								解决方法就是：平时不要把该描述符放进eventpoll结构体中，
								当需要写该fd的时候，调用epoll_ctl把fd加入eventpoll里监听，
								可写的时候就往里写，写完再次调用epoll_ctl把fd移出eventpoll，
								这种方法在发送很少数据的时候仍要执行两次epoll_ctl操作，有一定的操作代价。
								改进一下就是：平时不要把该描述符放进eventpoll结构体中，需要写的时候调用write或者send写数据，如果返回值是EAGAIN（写缓冲区满了），那么这时候才执行上面的步骤。 
								
					边缘触发：可读可写时，只会通知一次，效率比较高，但是要注意把数据读取完整
					总结：
					对于读写的client_fd，水平触发模式下，阻塞和非阻塞效果都一样，不过为了防止特殊情况，还是建议设置非阻塞。 
					对于读写的client_fd，边缘触发模式下，必须使用非阻塞IO，并要一次性全部读写完数据。
					*/
					set_fd_nonblock(client_fd);//设置客户端fd为非堵塞
					ev.events = EPOLLIN | EPOLLET;//设置为可读+边缘触发模式
					ev.data.fd = client_fd;
					if (0 > epoll_ctl(ep_fd, EPOLL_CTL_ADD, client_fd, &ev)) {//把连接的socket加入监听结构体
						PRINTF_ERR_MSG("epoll_ctl: EPOLL_CTL_ADD");
					}
				}
				
                //已经处理完所有连接，accept返回-1，errno为EAGAIN，accept出错返回-1，errno另有其值
                if (client_fd == -1) {
                    if (errno != EAGAIN && errno != ECONNABORTED 
                            && errno != EPROTO && errno != EINTR) {
                        perror("accept");
					}
				}
                
				continue;//接收连接完毕，直接开始下一次循环，也就是不执行这次循环后面的部分了
			}
			
			if (events[i].events & EPOLLIN) {//可读事件
				memset(rcv_buf, 0, rcv_buf_size);
			 	rcv_len = socket_recv_again(events[i].data.fd, (void *)rcv_buf, rcv_buf_size - 1);
				if (0 > rcv_len) {
                    PRINTF_ERR_MSG("socket_recv_again error");
				} else if (0 == rcv_len) {//客户端断开连接了
				/*test
					if (0 > getpeername(events[i].data.fd, (struct sockaddr *)&client_addr, &addrlen)) {//根据socket_fd获取客户端ip或端口信息
						PRINTF_MSG("Client closed, fd=%d", events[i].data.fd);
					} else {
						PRINTF_MSG("Client closed, fd=%d ip=%s, port=%d", events[i].data.fd, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
					}
				*/	
				
					//先保存一下当前退出的客户端信息
					client_data_t *exit_client = get_socket_client(&server->client_list, events[i].data.fd);
                    if (exit_client) {
                        delete_socket_client(&server->client_list, events[i].data.fd);//从链表中删除
    					if (server->client_onExit) {//客户端退出时的外部回调函数
    						server->client_onExit(server, *exit_client);
    					}
                    }
                    
					epoll_ctl(ep_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);//删除事件
					if (0 < events[i].data.fd) {
                        close(events[i].data.fd);//关闭该socket句柄
                    }
                } else {
					//printf("%s\n", rcv_buf);
                    client_data_t *on_client = get_socket_client(&server->client_list, events[i].data.fd);
                    if (on_client) {
                        if (server->client_onMessage) {//接收客户端消息时的外部回调函数
                            server->client_onMessage(server, *on_client, (void *)rcv_buf, rcv_len);
                        }
                    }

#if 0
					//接收到客户端数据之后就发送响应信息给客户端，设置可写事件，让下一个循环返回信息
					ev.data.fd = events[i].data.fd;
					ev.events = EPOLLOUT;//设置可写
					if (0 > epoll_ctl(ep_fd, EPOLL_CTL_MOD, events[i].data.fd, &ev)) {
						PRINTF_ERR_MSG("epoll_ctl: EPOLL_CTL_MOD");
					}
#endif
				}
            }
#if 0
			if (events[i].events & EPOLLOUT) {//可写事件
				char *send_str = "Hello";
				n = writen(events[i].data.fd, (void *)send_str, strlen(send_str));
				if (n != strlen(send_str)) {
					PRINTF_ERR_MSG("writen error");
				}
				
				ev.data.fd = events[i].data.fd;
                ev.events = EPOLLIN | EPOLLET;//数据写完毕，就设置为可读
                if (0 > epoll_ctl(ep_fd, EPOLL_CTL_MOD, events[i].data.fd, &ev)) {
                    PRINTF_ERR_MSG("epoll_ctl: EPOLL_CTL_MOD");
                }
            }
#endif
		} 
	}

done:
	if (ep_fd > 0) {
		close(ep_fd);
	}
	if (listen_fd > 0) {
		close(listen_fd);
	}
    
    client_node_t* current = server->client_list.head;//关闭所有客户端连接
    while (current != NULL) {
        if (0 < current->data.socket_fd) {
            close(current->data.socket_fd);
        }
        current = current->next;
    }
    destroy_list(&server->client_list);
	
	if (events) {		
		//释放资源
		free(events);
		events = NULL;
	}

	if (rcv_buf) {
		free(rcv_buf);
		rcv_buf = NULL;
	}
	
	PRINTF_MSG("exit epoll_socket_server");
	return NULL;
}

//启动一个socket服务端（这是一个线程服务）
int start_socket_server(socket_server_t *server)
{
	int ret = 0;
    if (0 == server->type) {
        ret = pthread_create(&server->thread_id, NULL, socket_server_loop, server);
        if (ret < 0) {
            PRINTF_ERR_MSG("pthread_create socket_server_loop is failed");
            return -1;
        }
    } else {
        ret = pthread_create(&server->thread_id, NULL, epoll_socket_server_loop, server);
        if (ret < 0) {
            PRINTF_ERR_MSG("pthread_create epoll_socket_server_loop is failed");
            return -1;
        }
    }

	server->run_flag = 1;
	return 0;
}

int close_socket_server(socket_server_t *server)
{
	if (server->run_flag) {
		server->run_flag = 0;
		pthread_join(server->thread_id, NULL);
	}
	
	return 0;
}

int restart_socket_server(socket_server_t *server)
{
    close_socket_server(server);
    return start_socket_server(server);

}
